SQLite en producción: no es solo para móviles

Tarjeta de memoria SSD en primer plano sobre superficie oscura representando almacenamiento persistente

El mito: SQLite es para apps móviles y prototipos. La realidad en 2024: miles de aplicaciones productivas — desde Tailscale hasta Fly.io, Expensify, o pequeños SaaS — lo usan como BD principal. La combinación de SQLite + WAL mode + Litestream + (opcionalmente) LiteFS hace posible escalar donde muchos equipos asumen que necesitan PostgreSQL. Este artículo cubre cómo, cuándo, y sus límites reales.

Por qué SQLite es viable serverside

Las asunciones tradicionales sobre SQLite están obsoletas:

  • “No escala”: falso. SQLite maneja miles de writes/s y cientos de miles de reads/s en hardware modesto.
  • “Concurrent writes no”: cierto históricamente, pero WAL mode mitiga mucho.
  • “No hay replicación”: Litestream, LiteFS, rqlite resuelven.
  • “No hay backup tool”: Litestream stream a S3 compatible.

Para apps con 1 proceso / 1 instancia, SQLite es espectacularmente productivo.

WAL mode: el cambio fundamental

Write-Ahead Logging, habilitable con PRAGMA journal_mode=WAL:

  • Reads concurrentes no bloquean writes.
  • Writes concurrentes aún se serializan pero con menos contención.
  • Mejor durabilidad vs journal_mode tradicional.
  • Checkpointing incremental de WAL a BD.

Casi todo deployment serio de SQLite usa WAL. Es el default razonable.

Optimizaciones productivas

Pragmas recomendados para producción:

PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;  -- vs FULL: trade-off durabilidad/perf
PRAGMA cache_size = -64000;   -- 64MB
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;   -- 5s antes de SQLITE_BUSY
PRAGMA temp_store = MEMORY;
PRAGMA mmap_size = 268435456; -- 256MB memory-mapped

Diferencias de rendimiento 3-10x comparado con defaults.

Litestream: replicación a S3

Litestream streams el WAL a S3 (o compatible) en near-realtime:

# /etc/litestream.yml
dbs:
  - path: /data/app.db
    replicas:
      - type: s3
        bucket: my-backups
        path: app.db
        region: eu-west-1
        access-key-id: ${AWS_ACCESS_KEY_ID}
        secret-access-key: ${AWS_SECRET_ACCESS_KEY}

Restore trivial:

litestream restore -o /data/app.db s3://my-backups/app.db

Para backup productivo, Litestream hace SQLite casi comparable a Postgres en durabilidad.

LiteFS: replicación multi-nodo

LiteFS (de Fly.io) lleva más lejos: replica completa de SQLite entre nodos con FUSE filesystem.

  • Primary + replicas: writes al primary, reads en cualquier nodo.
  • Consistencia fuerte con leasing.
  • Failover automatizado.

Útil para aplicaciones que requieren alta disponibilidad o globales (Fly.io usa esto para su propio Postgres clustering alternativo).

rqlite: SQLite distribuida

rqlite es otro enfoque: SQLite con Raft para consenso distribuido. Más complicado pero full-cluster.

Para casos de 3+ nodos con failover automático y replicación estricta, rqlite es más robusto que LiteFS.

Casos reales

  • Tailscale: coordination server en SQLite + Litestream.
  • Expensify: Bedrock (wrapper distribuido) en SQLite, millones de usuarios.
  • PocketBase: BaaS completo basado en SQLite.
  • Linear y Notion: SQLite en cliente via WASM.

Patrón común: un nodo aplicación + SQLite local + Litestream offsite.

Cuándo SQLite gana vs Postgres

Casos donde SQLite es mejor elección:

  • App con 1 proceso (single instance VM, Lambda monolítico).
  • Queries simples a moderadas, no cross-server joins.
  • Volumen de datos hasta ~100GB cómodamente, ~1TB con disciplina.
  • Latencia cero a la BD (está en el mismo proceso).
  • Despliegue simple (no BD server separado).
  • Backup como cp del archivo.

Cuándo SQLite NO basta

Honestidad:

  • Múltiples procesos escribiendo: serialización de writes es bottleneck.
  • Múltiples instancias de app: no puedes shard SQLite entre servers sin complejidad.
  • Queries analíticos masivos: usa DuckDB (SQLite es OLTP, DuckDB es OLAP).
  • Extensiones Postgres-specific (pgvector serio, PostGIS, etc).
  • Roles/permisos a nivel BD: SQLite no tiene.

Concurrencia de escritura

Para write-heavy apps, SQLite tiene un write global lock. Strategies:

  • Batching writes: transacciones pequeñas agrupadas.
  • BEGIN IMMEDIATE para upgrade a write lock antes de retry-dolor.
  • Cola en aplicación: un único escritor que procesa requests.
  • Segregación: datos write-heavy en BD separada del read-heavy.

Para ~1000 writes/s sostenidos, SQLite maneja. >10k writes/s sostenidos, considera Postgres.

Migraciones

Usar sqlc (Go), SQLx (Rust), better-sqlite3 (Node). Tooling comparable a Postgres.

Para migraciones de schema:

Diferencias de SQL específicas (tipos, ALTER TABLE limitations) exigen care.

Extensiones útiles

Crecimiento del ecosistema reciente es fuerte.

Encriptación

Para datos sensibles at-rest, estas herramientas son maduras.

Conclusión

SQLite es una opción seria para aplicaciones productivas 1-instance. Con WAL mode, pragmas optimizados, Litestream, y opcionalmente LiteFS o rqlite para HA, cubre casos que muchos equipos asumen que requieren PostgreSQL. La ventaja operativa es enorme: sin BD server, sin red, sin permisos, sin backup complejo. Para cargas write-heavy masivas o multi-instance-writing, Postgres sigue siendo correcto. Pero para la enorme mayoría de apps pequeñas-medianas, empezar con SQLite y crecer hacia Postgres solo si realmente hace falta es estrategia pragmática.

Síguenos en jacar.es para más sobre SQLite, bases de datos y arquitecturas simples.

Entradas relacionadas